package infoplaces2;

import static infoplaces2.GoogleStreetMap.getContentOfURL;
import static infoplaces2.GoogleStreetMap.getXMLLatitude;
import static infoplaces2.GoogleStreetMap.getXMLLongitude;
import static infoplaces2.GoogleStreetMap.getXMLPanoID;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Set;
import support.Ponto;
import support.Triangulacao;

public class InfoPlaces2 {

    private final GoogleStreetMap streetMap = new GoogleStreetMap();
    private final int sizeBin;
    private final int sizeWindow;

    //posso ter um método que chama run, que também poderá chamar triangulacaoEPlot dps que chamar a run.
    public InfoPlaces2() {
           this.sizeBin = 1; //Representa o valor para agrupar os graus. Ex.: 1 em 1 grau
           this.sizeWindow = 5;
    }
    
    

    public Ponto run(ArrayList<ProcessedStreetNode> winners, ArrayList<ProcessedStreetNode> listProcessedStreetNode, Photo photo, int n) throws IOException, InterruptedException {
        winners = findWinners(photo.getLatitude(), photo.getLongitude(), photo, listProcessedStreetNode, n);
        Ponto alvo = triangulacao(winners); //Ponto alvo precisa ser usado para fazer o plot
        return alvo;
    }

    private <EnvironmentMapType extends PanoramicImage, NeighbourIDType> ArrayList<ProcessedStreetNode> findWinners(double latitude, double longitude, Photo photo, ArrayList<ProcessedStreetNode> listProcessedStreetNode, int n) throws IOException, InterruptedException {
        String myXML = getContentOfURL("http://cbk0.google.com/cbk?output=xml&ll=" + latitude + "," + longitude);
        String rootID = getXMLPanoID(myXML);
            
        ProcessedStreetNode winner = null;
        ProcessedStreetNode viceWinner = null;

        ArrayList<ProcessedStreetNode> winners = new ArrayList<>();

        LinkedList<NeighbourIDType> fila = new LinkedList<>(); //NeighbourIDType é uma String, quando falamos do ID do Google
        fila.addLast((NeighbourIDType) rootID);

        Set<NeighbourIDType> setList = new LinkedHashSet<>(); // preciso generalizar pra qualquer ID
        //esse setList precisa ser criado para criar um lista com todos os nós percorridos para uma busca por vencedores.
//        setList.add((NeighbourIDType) rootID);

        boolean winnersFound = false;
        //ATENÇÃO: é preciso estabelecer um critério com relação ao histograma para definir uma condição a mais a ser inserida no loop

        do {
            StreetNode<?, ?> streetNode = streetMap.addNode((String) fila.removeFirst()); //TODO O cast para String não deveria ser necessário
            //é criado um streetNode ao se adicionar o primeiro nó da fila no streetMap.
            //o primeiro nó é excluído da fila.

            setList.add((NeighbourIDType) streetNode.getID()); //TODO O cast para NeighbourIDType não deveria ser necessário
            //agora o primeiro nó da lista já foi processado, e excluído da fila, falta adicionar seu ID no setList, para que fique registrado que este nó já foi processado

            ProcessedStreetNode processedStreetNode = new ProcessedStreetNode(streetNode, photo, this.sizeBin, this.sizeWindow);
            //é criada uma instancia de um nó processado (processedStreetNode) a partir do nó e da foto.
            // é de sua responsabilidade comparar a imagem equirectangular do google associada ao ponto panoramico com a foto tirada.

            listProcessedStreetNode.add(processedStreetNode); //adiciona um nó processado na lista de nós processados, ao final do loop essa lista terá todos os nós processados percorridos para encontrar os nós vencedores

            int matchesAtual = processedStreetNode.getMacthesCount();
            
            //IDENTIFICAR OS VENCEDORES é incubencia do processedStreetNode
            //ADD EM WINNERS
            if(matchesAtual > 0){
                winners.add(processedStreetNode);
            }else{ //para debug
                if(matchesAtual == 0){
                    System.out.println("Nenhum match foi encontrado!");
                }else{
                    System.out.println("A vaca foi pro brejo!");
                }
            }
            
            
            if (winners.size() == n) { //se já encontrou os N vencedores não precisa mais enfileirar ninguem
                winnersFound = true;
            } else {
                // Enfileira os vizinhos do nó atual
                for (int i = 0; i < streetNode.neighboursCount(); i++) {
                    if (!setList.contains((NeighbourIDType) streetNode.getNeighbourID(i)) && nodeIsNear(rootID, streetNode.getNeighbourID(i))) {
                        //verificar se os filhos do streetNode atual já foram processados, caso sim, não adicioná-los na fila novamente
                        fila.addLast((NeighbourIDType) streetNode.getNeighbourID(i));
                    }
                }
            }

        }while (!winnersFound && !fila.isEmpty() );

        return winners;
    }

    private <NeighbourIDType> boolean nodeIsNear(NeighbourIDType nodeID, NeighbourIDType neighbourID) throws IOException {

        //MELHORAR
        String myXML1 = getContentOfURL("http://cbk0.google.com/cbk?output=xml&panoid=" + nodeID);
        double lat1 = getXMLLatitude(myXML1);
        double lon1 = getXMLLongitude(myXML1);
        
        String myXML2 = getContentOfURL("http://cbk0.google.com/cbk?output=xml&panoid=" + neighbourID);
        double lat2 = getXMLLatitude(myXML2);
        double lon2 = getXMLLongitude(myXML2);

        System.out.println("lat1 = " + lat1 + "\tlon1 = " + lon1 + "\nlat2 = " + lat2 + "\tlon2 = " + lon2);
        

        final int R = 6371; // Radius of the earth

        Double latDistance = deg2rad(lat2 - lat1);
        Double lonDistance = deg2rad(lon2 - lon1);
        Double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
                + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2))
                * Math.sin(lonDistance / 2) * Math.sin(lonDistance / 2);
        Double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
        double distance = R * c; // convert to meters

        distance = Math.pow(distance, 2) + Math.pow(0, 2);
        double finalDistance = Math.sqrt(distance);
        System.out.println("Distancia entre lat1 e lat2 em km: "+finalDistance);
        System.out.println("");

        if (finalDistance < 1) {
            return true;
        } else {
            System.out.println("*\n*\n*\n*\n*\n*\n*\n*\n*\n* MAIOR DO QUE 1 *\n*\n*\n*\n*\n*\n*\n*\n*\n*");
            return false;
        }
    }

    private double deg2rad(double deg) {
        return (deg * Math.PI / 180.0);
    }

    private Ponto triangulacao(ArrayList<ProcessedStreetNode> winners) throws IOException, InterruptedException {

        Ponto winnerPoint = new Ponto(winners.get(0).getStreetNode().getLongitude(), winners.get(0).getStreetNode().getLatitude());
        double winnerAngle = winners.get(0).getDegreeMaximum();
        System.out.println("\nangulo vencedor: "+winnerAngle);

        Ponto viceWinnerPoint = new Ponto(winners.get(1).getStreetNode().getLongitude(), winners.get(1).getStreetNode().getLatitude());
        double viceWinnerAngle = winners.get(1).getDegreeMaximum();
        System.out.println("angulo vice-vencedor: "+viceWinnerAngle);
        
        Ponto target = Triangulacao.run(winnerPoint, viceWinnerPoint, winnerAngle, viceWinnerAngle);

        //salvarLogTriangulacao
        String winnerID = (String) winners.get(0).getStreetNode().getID();
        String viceWinnerID = (String) winners.get(1).getStreetNode().getID();

        File arq = new File("temp/Log_Triangulacao.txt");

        FileWriter fw = new FileWriter(arq);
        BufferedWriter bw = new BufferedWriter(fw);

        bw.write("Vencedor: " + winnerID);
        bw.newLine();
        bw.write("Lat = " + winnerPoint.get_y() + " Lng = " + winnerPoint.get_x());
        bw.newLine();
        bw.write("Direcao: " + winnerAngle);
        bw.newLine();
        bw.write("--------------");
        bw.newLine();
        bw.write("ViceVencedor: " + viceWinnerID);
        bw.newLine();
        bw.write("Lat = " + viceWinnerPoint.get_y() + " Lng = " + viceWinnerPoint.get_x());
        bw.newLine();
        bw.write("Direcao: " + viceWinnerAngle);
        bw.newLine();
        bw.write("--------------");
        bw.newLine();
        bw.write("Resultado da triangulacao: Lat = " + target.get_y() + " Lng = " + target.get_x());
        bw.close();

        return target;
    }

}

//    private static void salvarLogTriangulacao(Ponto p1, Ponto p2, double a1, double a2, Ponto resultado, 
//            String panoIdVencedor, String panoIdViceVencedor) throws IOException {
//        File arq = new File("temp/Log_Triangulacao.txt");
//
//        FileWriter fw = new FileWriter(arq);
//        BufferedWriter bw = new BufferedWriter(fw);
//
//        bw.write("Vencedor: " + panoIdVencedor);
//        bw.newLine(); 
//        bw.write("Lat = " + p1.get_y() + " Lng = " + p1.get_x());
//        bw.newLine();  
//        bw.write("Direcao: " + a1);
//        bw.newLine();  
//        bw.write("--------------");
//        bw.newLine();  
//        bw.write("ViceVencedor: " + panoIdViceVencedor);
//        bw.newLine(); 
//        bw.write("Lat = " + p2.get_y() + " Lng = " + p2.get_x());
//        bw.newLine();  
//        bw.write("Direcao: " + a2);
//        bw.newLine(); 
//        bw.write("--------------");
//        bw.newLine();  
//        bw.write("Resultado da triangulacao: Lat = " + resultado.get_y() + " Lng = " + resultado.get_x());
//        bw.close();
//
//    }
